package com.ftqh.tutorial.security.oauth2.controller;

import com.ftqh.tutorial.security.oauth2.provider.client.OAuth2ClientDetailsService;
import lombok.extern.java.Log;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.ConsumerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by Administrator on 2017/6/27.
 */
@RestController
@RequestMapping("/oauth/tokens")
@PreAuthorize("hasRole('ROLE_ADMIN')")
@Log
public class TokenController {

    // N.B. the @Qualifier here should not be necessary (gh-298) but lots of users report
    // needing it.
    @Qualifier("consumerTokenServices")
    private ConsumerTokenServices tokenServices;
    private TokenStore tokenStore;
    private OAuth2ClientDetailsService clientDetailsService;

    TokenController(@Qualifier("consumerTokenServices")ConsumerTokenServices tokenServices,
                    TokenStore tokenStore,OAuth2ClientDetailsService clientDetailsService){
        this.tokenServices = tokenServices;
        this.tokenStore = tokenStore;
        this.clientDetailsService = clientDetailsService;
    }

    @GetMapping(produces = {"application/json"})
    public HashMap<String, Collection<OAuth2AccessToken>> listAllTokens() {
        HashMap<String, Collection<OAuth2AccessToken>> result = new HashMap<>();
        clientDetailsService.listOAuth2ClientDetails().forEach(entity -> result.put(entity.getClientId(),
                enhance(tokenStore.findTokensByClientId(entity.getClientId()))));
        return result;
    }

    @GetMapping
    public ModelAndView tokenAdminPage() {
        ModelAndView model = new ModelAndView("oauth/tokens");
        HashMap<String, Collection<OAuth2AccessToken>> result = listAllTokens();
        model.addObject("tokens", result);
        return model;
    }

    @DeleteMapping("/{username}/{token}")
    public ResponseEntity<String> revokeToken(@PathVariable("username") String username,
                                              @PathVariable("token")  String token, Principal principal) throws Exception {
        checkResourceOwner(username, principal);
        if (tokenServices.revokeToken(token)) {
            return ResponseEntity.ok("success");
        } else {
            return ResponseEntity.ok("empty");
        }
    }

    private void checkResourceOwner(String user, Principal principal) {
        if (principal instanceof OAuth2Authentication) {
            OAuth2Authentication authentication = (OAuth2Authentication) principal;
            if (!authentication.isClientOnly() && !user.equals(principal.getName())) {
                throw new AccessDeniedException(
                        String.format("User '%s' cannot obtain tokens for user '%s'",
                                principal.getName(), user));
            }
        }
    }

    private Collection<OAuth2AccessToken> enhance(Collection<OAuth2AccessToken> tokens) {
        Collection<OAuth2AccessToken> result = new ArrayList<>();
        for (OAuth2AccessToken prototype : tokens) {
            DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(prototype);
            OAuth2Authentication authentication = tokenStore.readAuthentication(token);
            if (authentication == null) {
                continue;
            }
            String userName = authentication.getName();
            if (StringUtils.isEmpty(userName)) {
                userName = "Unknown";
            }
            Map<String, Object> map = new HashMap<>(
                    token.getAdditionalInformation());
            map.put("user_name", userName);
            token.setAdditionalInformation(map);
            result.add(token);
        }
        return result;
    }
}
